Dummy Object
The book has now been published and the content of this chapter has likely changed substanstially.Please see page 728 of xUnit Test Patterns for the latest information.
Also known as: Dummy, Dummy Parameter, Dummy Value, Placeholder, Stub
Variation of: Test Double
How do we specify the values to be used in tests when the only usage is as irrelevant arguments of SUT method calls?
We pass an object that has no implementation as an argument of a method called on the SUT.
Invoice inv = new Invoice( new DummyCustomer() ); Example DummyValueSketch embedded from java/com/clrstream/camug/example/test/InvoiceTest.java
Getting the system under test (SUT) into the right state to start a test often requires calling other methods of the SUT. These methods often take as arguments objects that are stored in instance variables for later use. Often, these objects (or at least some attributes of these objects) are never used in the code that we are actually testing so we are only creating them to conform to the signature of some method we have to call to get the SUT into the right state. Constructing these objects can be non-trivial and adds unnecessary complexity to the test.
In these cases, a Dummy Object can be passed as an argument removing the need to build a real object.
How It Works
We create an instance of some object that can be instantiated easily and with no dependencies and we pass it as the parameter to the method of the SUT. Since it won't actually be used within the SUT we don't need any implementation for this object. If any of the methods of the Dummy Object are invoked, the test really should throw an error and trying to invoke a non-existent method will typically have that result.
When To Use It
We can use a Dummy Object whenever we need to use objects as attributes of other objects or arguments of methods on the SUT or other fixture objects. Using a Dummy Object helps avoid Obscure Test (page X) by leaving out the irrelevant code that would be necessary to build real objects and by making it clear which objects and values are not used by the SUT.
If we need to control the indirect inputs or verify the indirect outputs of the SUT, we should probably be using a Test Stub (page X) or Mock Object (page X) instead. If the object will be used by the SUT but we cannot provide the real object, we should consider providing a Fake Object (page X) that provides just enough behavior for the test to execute.
We can use one of the value patterns when the SUT really does need to use the object in some. We can use either a Literal Value (page X), a Generated Value (page X) or a Derived Value (page X) depending on the circumstance.
Variation: Dummy Argument
We can use a Dummy Argument whenever methods of the SUT take objects as arguments (From Wikipedia: Parameters are also commonly referred to as arguments, though arguments are more properly thought of as the actual values or references assigned to the parameter variables when the subroutine is called at runtime. When discussing code that is calling into a subroutine, any values or references passed into the subroutine are the arguments, and the place in the code where these values or references are given is the parameter list. When discussing the code inside the subroutine definition, the variables in the subroutine's parameter list are the parameters, while the values of the parameters at runtime are the arguments.)and those objects are not relevant to the test.
Variation: Dummy Attribute
We can use a Dummy Attribute whenever we are creating objects that will be used as part of the fixture or as arguments of SUT methods and some of the attributes of those objects are not relevant to the test.
Implementation Notes
The simplest implementation of Dummy Object is to pass a null as the parameter. This works even in statically typed language like Java but only if the method being called doesn't check for null parameters. If it complains when we pass it a null, we'll need to employ a slightly more sophisticated implementation. The biggest disadvantage to using null is that it is not very descriptive.
In dynamically typed languages such as Ruby, Perl and Python the actual type of the object will never be checked (since it will never be used) so we can use any class such as String or Object. It is useful to give the object a Self-Describing Value (see Literal Value) such as "Dummy Customer".
In statically typed languages (such as Java, C# and C++) we must ensure that the Dummy Object is type-compatible with the parameter it is to match. This is much easier to do if the parameter has an abstract type (e.g. an Interface in Java) because we can create our own trivial implementation of the type. If the parameter type is a concrete class, we may be able to create a trivial instance of it or we may need to create an instance of a Test-Specific Subclass (page X) within our test.
Some Mock Object frameworks have Test Utility Methods (page X) that will generate a Dummy Object for a specified class taking a String argument for a Self-Describing Value.
Note that while the Dummy Object may in fact be null, a Dummy Object is not the same as a Null Object[PLOPD3]. A Dummy Object is not used by the SUT so its behavior is irrelevant while a Null Object is used by the SUT but is designed to do nothing. A small but very important distinction!
Motivating Example
In this example, we are testing the Invoice but we require a Customer to instantiate the invoice. The Customer requires an Address which requires a City so we find ourselves creating all these additional objects just to set up the fixture. If we know that the behavior we are testing should not access the Customer at all, why do we need to create it and all the objects it depends on?
public void testInvoice_addLineItem_noECS() { final int QUANTITY = 1; Product product = new Product(getUniqueNumberAsString(), getUniqueNumber()); State state = new State("West Dakota", "WD"); City city = new City("Centreville", state); Address address = new Address("123 Blake St.", city, "12345"); Customer customer= new Customer(getUniqueNumberAsString(), getUniqueNumberAsString(), address); Invoice inv = new Invoice(customer); // Exercise inv.addItemQuantity(product, QUANTITY); // Verify List lineItems = inv.getLineItems(); assertEquals("number of items", lineItems.size(), 1); LineItem actual = (LineItem)lineItems.get(0); LineItem expItem = new LineItem(inv, product, QUANTITY); assertLineItemsEqual("",expItem, actual); } Example EntityChain embedded from java/com/clrstream/camug/example/test/InvoiceTest.java
This test is quite cluttered as a result of all this extra object creation. How is the behavior we are testing related to the Address and City? From this test, we can only assume that there is some relation. But this misleads the test reader!
Refactoring Notes
If the objects in the fixture are not relevant to the test, they should not be visible in the test. Therefore, we should try to remove the need for creating all these objects. We could try passing in null for the Customer but in this case, the the constructor checks for null and rejects it. So we have to find another way.
The solution is to replace the object that is not important to our test with a Dummy Object. In dynamically-typed languages, we could just pass a string but in statically-typed languages like Java and C# we must pass a type-compatible object. In this case, we have chose to do an Extract Interface[Fowler] refactoring on Customer to create a new interface and then create a new implementation class DummyCustomer. Of course, as part of the Extract Interface refactoring we must replace all references to Customer with the new interface name so that the DummyCustomer will be acceptable. A less instrusive option would be to use a Test-Specific Subclass of Customer that adds a test-friendly constructor.
Example: Dummy Values and Dummy Objects
Here's the same test using a Dummy Object instead of the Product name and the Customer. Note how much simpler the fixture setup has become!
public void testInvoice_addLineItem_DO() { final int QUANTITY = 1; Product product = new Product("Dummy Product Name", getUniqueNumber()); Invoice inv = new Invoice( new DummyCustomer() ); LineItem expItem = new LineItem(inv, product, QUANTITY); // Exercise inv.addItemQuantity(product, QUANTITY); // Verify List lineItems = inv.getLineItems(); assertEquals("number of items", lineItems.size(), 1); LineItem actual = (LineItem)lineItems.get(0); assertLineItemsEqual("", expItem, actual); } Example DummyObject embedded from java/com/clrstream/camug/example/test/InvoiceTest.java
Using a Dummy Object for the name of the Product was simple because it is a string and has no uniqueness requirement. We were able to use a Self-Describing Value. We were not able to use a Dummy Object for the Product number because it must be unique so we left it as a Generated Value. The Customer was a bit trickier because the constructor expected a non-nil object. Because this example is in Java, the method parameter is strongly typed so we needed to create an alternate implementation of the ICustomer interface with a no argument constructor to simplify inline construction. Because the DummyCustomer is never used, we have created it inline rather than declaring a variable to hold it. This reduces the fixture setup code by one line and the presence of inline constructor call within the call to the Invoice constructor reinforces the message that we only need the Dummy Object for the constructor call and not for the rest of the test. Here is the code for the DummyCustomer:
public class DummyCustomer implements ICustomer { public DummyCustomer() { // Real simple; nothing to initialize! } public int getZone() { throw new RuntimeException("This should never be called!"); } } Example DummyObjectDefn embedded from java/com/clrstream/camug/example/test/DummyCustomer.java
We have implemented the DummyCustomer class with just those methods declared in the interface; each method throws an exception so we know if it is ever hit. We could also have used a Pseudo Object (see Hard-Coded Test Double on page X) for the DummyCustomer. In other circumstances we might have been able to simply pass in a null or construct a dummy instance of the real class; the main issue with the latter is that we won't know for sure if the Dummy Object is actually used.
Further Reading
Note that when [UTwJ] refers to the use of a "dummy object", what they are referring to in this book's terminology is a Test Stub. See the appendix Mocks, Fakes, Stubs and Dummies for a more thorough comparison of the terminology used in various books and articles. The JMock and NMock frameworks for testing with Mock Objects have support for auto-generation of Dummy Objects.
Copyright © 2003-2008 Gerard Meszaros all rights reserved